package com.xiaolun;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;

/**
 * @Author 硕鼠
 * @Date 2020/6/15 11:43
 * @Version 1.0
 * @Description 文件说明
 */
public class FileServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //判断上传的文件是普通表单还是带文件的表单
        if( !ServletFileUpload.isMultipartContent(req)){
            return; //终止方法运行，说明这是一个普通的表单，直接返回。
        }

        /**
         * 1. 通过这个if说明是文件了
         * 2. 创建上传文件的保存路径，建议保存到web-inf路径下，用户无法访问。
         **/
        String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
        File uploadFile = new File(uploadPath);
        //如果上传文件不存在,就创建这个目录。
        if (!uploadFile.exists()){
            uploadFile.mkdir();
        }

        /**
         * 1. 缓存，临时文件。
         * 2. 临时路径，假如文件超过了预期的大小，我们就把它放到一个临时文件中，过几天自动删除，或者提醒用户转为永久。
         */
        String tmpPath = this.getServletContext().getRealPath("/WEB-INF/tmp");
        File file = new File(tmpPath);
        //如果上传文件不存在， 创建这个目录。
        if (!file.exists()){
            file.mkdir();
        }

        /**
         * 处理上传的文件，一般都需要通过借助Apache上传组件来获取，commons-fileupload，它依赖于commons-io组件。
         */
        try {
            //1.创建`DiskFileItemFactory`对象，处理文件上传路径或者大小限制的。
            // 将请求消息实体中的每一个项目封装成单独的DiskFileItem (FileItem接口的实现) 对象的任务。
            DiskFileItemFactory factory = getDiskFileItemFactory(file);
            //2.获取ServletFileUpload，ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成
            //一个FileItem对象中。
            ServletFileUpload upload = getServletFileUpload(factory);
            //3.处理上传文件
            String msg = uploadParseRequest(upload,req,uploadPath);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //servlet请求转发消息
        req.setAttribute("msg", "文件上传成功");
        req.getRequestDispatcher("/info.jsp").forward(req, resp);
    }

    /**
     *
     * @param upload
     * @param req
     * @param uploadPath
     * @return
     * @throws Exception
     */
    private static String uploadParseRequest(ServletFileUpload upload, HttpServletRequest req, String uploadPath) throws Exception {
        List<FileItem> fileItems = fileItems = upload.parseRequest(req);

        //表单有好多项，我们需要遍历。
        for (FileItem fileItem : fileItems) {
            //判断上传的文件是普通的表单还是带文件的表单
            if (fileItem.isFormField()){ //正常处理普通的表单。
                //getFieldName()指的是前端表单控件的name。
                String name = fileItem.getFieldName();
                //处理乱码问题
                String value = fileItem.getString("UTF-8");
                System.out.println(name + ":" + value);
            }else {
                /**
                 * 1.处理文件表单。
                 * 2.拿到文件名字getName()对应表单中的name属性。
                 */
                String uploadFileName = fileItem.getName();
                System.out.println("上传的文件名为："+uploadFileName);
                //上传文件名字不合法，让它不上传。
                if (uploadFileName.trim().equals("") || uploadFileName == null){
                    continue;
                }

                /**
                 * 1.获取上传的文件名。
                 * 2.如果文件后缀名，不是我们所需要的，就直接return，不处理，告诉用户文件类型不对。
                 */
                String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
                String fileExName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);
                System.out.println("文件信息【文件名"+fileName+ "----文件类型："+fileExName);
                /**
                 * 1.使用UUID(唯一识别的通用码)，保证文件名唯一。
                 * 2.UUID.randomUUID()随机生成一个唯一识别的通用码。
                 * 3.创建实体类的时候，实现这个序列化接口：implements Serializable。
                 * 是一个标记接口，当虚拟运行到这里的时候，会实现一些方法。
                 */
                String uuidPath = UUID.randomUUID().toString();

                /**1.文件处理完毕，文件存放。
                 * 2.文件真实存在的路径 reanlPath。
                 */
                String realPath = uploadPath +"/" + uuidPath;
                //给每一个文件创建一个对应的文件夹
                File realPathFile = new File(realPath);
                if ( !realPathFile.exists()){
                    realPathFile.mkdir();
                }
                //-----------------------------------------
                /**
                 * 1.文件存放完毕，文件传输。
                 * 2.获得文件上传的流。
                 * 3.创建要给文件输出流。
                 */
                InputStream inputStream = fileItem.getInputStream();
                FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);

                //创建一个缓冲区
                byte[] buffer = new byte[1024*1024];
                //判断是否读取完毕
                int len = 0;
                //如果大于0说明还有数据
                while ((len = inputStream.read(buffer)) > 0){
                    fos.write(buffer,0,len);
                }

                //关闭流
                fos.close();
                inputStream.close();

                fileItem.delete();//上传成功，清除临时文件
            }
        }
        return null;
    }

    /**
     *
     * @param factory
     * @return
     */
    private static ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setProgressListener(new ProgressListener() {
            //pBytesRead已经读取到的文件大小
            //pContentLength文件大小
            @Override
            public void update(long pBytesRead, long pContentLength, int pItems) {
                System.out.println("总大小："+pContentLength+"已上传："+pBytesRead);
            }
        });
        //处理乱码问题
        upload.setHeaderEncoding("UTF-8");
        //设置单个文件的最大值
        upload.setFileSizeMax(1024*1024*10);
        //设置总共能够上传文件的大小
        //1024=1Kb*1024=1M*1-=10M
        upload.setSizeMax(1024*1024*10);
        return upload;
    }

    /**
     *
     * @param file
     * @return
     */
    private static DiskFileItemFactory getDiskFileItemFactory(File file){
        //1.创建`DiskFileItemFactory`对象，处理文件上传路径或者大小限制的。
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //通过这个工厂设置一个缓冲区，当上传的文件大于这个缓冲区的时候，将他放到临时文件中。
        factory.setSizeThreshold(1024*1024); //大小为1M.
        factory.setRepository(file);//临时文件保存目录。
        return factory;
    }
}
